﻿using System.Data;
using Microsoft.EntityFrameworkCore.Storage;
using Microsoft.Extensions.Logging;

namespace Known.EntityFramework;

class EFCoreDatabase : Data.Database
{
    private readonly ILoggerFactory loggerFactory;
    internal readonly DataContext ctx;

    public EFCoreDatabase(ILoggerFactory loggerFactory) : base(loggerFactory)
    {
        this.loggerFactory = loggerFactory;
        ctx = new DataContext();
        DatabaseType = EFCoreHelper.GetDatabaseType(ctx.Database.ProviderName);
        ConnectionString = ctx.Database.GetConnectionString();
    }

    public override object DbContext => ctx;

    public override void SetDatabase(string connName)
    {
    }

    public override Task OpenAsync()
    {
        return ctx.Database.OpenConnectionAsync();
    }

    public override Task CloseAsync()
    {
        return ctx.Database.CloseConnectionAsync();
    }

    public override Task BeginTransAsync()
    {
        return ctx.Database.BeginTransactionAsync();
    }

    public override async Task CommitTransAsync()
    {
        await ctx.SaveChangesAsync();
        await ctx.Database.CommitTransactionAsync();
    }

    public override Task RollbackTransAsync()
    {
        return ctx.Database.RollbackTransactionAsync();
    }

    public override IQueryBuilder<T> Query<T>() => new EFCoreQueryBuilder<T>(ctx.Set<T>());

    //public override Task<PagingResult<T>> QueryPageAsync<T>(string sql, PagingCriteria criteria, Func<T, ExportColumnInfo, object> onExport = null)
    //{
    //    throw new NotImplementedException();
    //}

    //public override Task<PagingResult<T>> QueryPageAsync<T>(PagingCriteria criteria, Func<T, ExportColumnInfo, object> onExport = null)
    //{
    //    throw new NotImplementedException();
    //}

    //public override Task<T> QueryAsync<T>(string sql, object param = null)
    //{
    //    throw new NotImplementedException();
    //}

    public override Task<T> QueryAsync<T>(Expression<Func<T, bool>> expression)
    {
        return ctx.Set<T>().FirstOrDefaultAsync(expression)!;
    }

    public override Task<List<T>> QueryListAsync<T>()
    {
        return ctx.Set<T>().ToListAsync();
    }

    //public override Task<List<T>> QueryListAsync<T>(string sql, object param = null)
    //{
    //    throw new NotImplementedException();
    //}

    public override Task<List<T>> QueryListAsync<T>(Expression<Func<T, bool>> expression)
    {
        return ctx.Set<T>().Where(expression).ToListAsync();
    }

    //public override Task<List<T>> QueryListByIdAsync<T>(string[] ids)
    //{
    //    throw new NotImplementedException();
    //}

    //public override Task<DataTable> QueryTableAsync(string sql, object param = null)
    //{
    //    throw new NotImplementedException();
    //}

    //public override Task<int> ExecuteAsync(string sql, object param = null)
    //{
    //    throw new NotImplementedException();
    //}

    //public override async Task<T> ScalarAsync<T>(string sql, object param = null)
    //{
    //    throw new NotImplementedException();
    //}

    //public override Task<List<T>> ScalarsAsync<T>(string sql, object param = null)
    //{
    //    throw new NotImplementedException();
    //}

    public override Task<int> CountAsync<T>()
    {
        return ctx.Set<T>().CountAsync();
    }

    public override Task<int> CountAsync<T>(Expression<Func<T, bool>> expression)
    {
        return ctx.Set<T>().CountAsync(expression);
    }

    public override Task<int> DeleteAsync<T>(Expression<Func<T, bool>> expression)
    {
        return ctx.Set<T>().Where(expression).ExecuteDeleteAsync();
    }

    public override Task<int> DeleteAllAsync<T>()
    {
        return ctx.Set<T>().ExecuteDeleteAsync();
    }

    //public override Task<int> InsertAsync<T>(T data)
    //{
    //    throw new NotImplementedException();
    //}

    //public override Task<int> InsertListAsync<T>(List<T> datas)
    //{
    //    throw new NotImplementedException();
    //}

    //public override Task<int> InsertTableAsync(DataTable data)
    //{
    //    throw new NotImplementedException();
    //}

    //public override Task<PagingResult<Dictionary<string, object>>> QueryPageAsync(string tableName, PagingCriteria criteria)
    //{
    //    throw new NotImplementedException();
    //}

    //public override Task<bool> ExistsAsync(string tableName, string id)
    //{
    //    throw new NotImplementedException();
    //}

    //public override Task<int> DeleteAsync(string tableName, string id)
    //{
    //    throw new NotImplementedException();
    //}

    //public override Task<int> InsertAsync(string tableName, Dictionary<string, object> data)
    //{
    //    throw new NotImplementedException();
    //}

    //public override Task<int> UpdateAsync(string tableName, string keyField, Dictionary<string, object> data)
    //{
    //    throw new NotImplementedException();
    //}

    protected override void CheckEntity<T>(T entity)
    {
        var existed = ctx.Set<T>().Find(entity.Id);
        entity.IsNew = existed == null;
    }

    protected override Task<int> SaveDataAsync<T>(T entity)
    {
        if (entity.IsNew)
        {
            ctx.Set<T>().Add(entity);
        }
        else
        {
            var existed = ctx.Set<T>().Find(entity.Id);
            ctx.Entry(existed).CurrentValues.SetValues(entity);
        }

        if (ctx.Database.CurrentTransaction == null)
            return ctx.SaveChangesAsync();

        return Task.FromResult(1);
    }

    protected override Data.Database CreateDatabase()
    {
        var db = new EFCoreDatabase(loggerFactory);
        db.Context = Context;
        db.User = User;
        return db;
    }

    protected override IDbCommand GetDbCommandAsync(CommandInfo info)
    {
        var conn = ctx.Database.GetDbConnection();
        var cmd = conn.CreateCommand();
        if (ctx.Database.CurrentTransaction != null)
        {
            var trans = ctx.Database.CurrentTransaction.GetDbTransaction();
            if (trans.Connection == null)
                throw new ArgumentException("The transaction was rollbacked or commited, please provide an open transaction.", nameof(trans));

            cmd.Transaction = trans;
        }
        else
        {
            if (conn.State != ConnectionState.Open)
                conn.Open();
        }

        return cmd;
    }

    protected override void Dispose(bool isDisposing)
    {
        base.Dispose(isDisposing);
    }
}